/**********************************************************
 * ActionScript Development Tool 
 * Copyright (C) 2005 asdt.org 
 * 
 * http://www.asdt.org
 * http://sourceforge.net/projects/aseclipseplugin/
 *
 * This program is free software; 
 * you can redistribute it and/or modify it under the terms of 
 * the GNU General Public License as published by the 
 * Free Software Foundation; either version 2 of the License, 
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the 
 * Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 
 * 
 **********************************************************/
package org.asdt.core.dom;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.asdt.core.internal.antlr.AS2Lexer;
import org.asdt.core.internal.antlr.AS2Parser;
import org.asdt.core.internal.antlr.AS2TokenTypes;
import org.asdt.core.internal.antlr.ASSourceAST;
import org.asdt.core.internal.antlr.ASSourceASTFactory;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;

import antlr.RecognitionException;
import antlr.Token;
import antlr.TokenStreamException;
import ca.scotthyndman.as2haxe.dom.ASArrayAccess;
import ca.scotthyndman.as2haxe.dom.ASArrayLiteral;
import ca.scotthyndman.as2haxe.dom.ASAssignment;
import ca.scotthyndman.as2haxe.dom.ASBlock;
import ca.scotthyndman.as2haxe.dom.ASBooleanLiteral;
import ca.scotthyndman.as2haxe.dom.ASBreakStatement;
import ca.scotthyndman.as2haxe.dom.ASCatchStatement;
import ca.scotthyndman.as2haxe.dom.ASComment;
import ca.scotthyndman.as2haxe.dom.ASCompilationUnit;
import ca.scotthyndman.as2haxe.dom.ASConditionalExpression;
import ca.scotthyndman.as2haxe.dom.ASConstructorDeclaration;
import ca.scotthyndman.as2haxe.dom.ASContinueStatement;
import ca.scotthyndman.as2haxe.dom.ASDoStatement;
import ca.scotthyndman.as2haxe.dom.ASExpression;
import ca.scotthyndman.as2haxe.dom.ASExpressionStatement;
import ca.scotthyndman.as2haxe.dom.ASFieldAccess;
import ca.scotthyndman.as2haxe.dom.ASFieldDeclaration;
import ca.scotthyndman.as2haxe.dom.ASForInStatement;
import ca.scotthyndman.as2haxe.dom.ASForStatement;
import ca.scotthyndman.as2haxe.dom.ASFunction;
import ca.scotthyndman.as2haxe.dom.ASFunctionExpression;
import ca.scotthyndman.as2haxe.dom.ASIfStatement;
import ca.scotthyndman.as2haxe.dom.ASImportDeclaration;
import ca.scotthyndman.as2haxe.dom.ASInfixExpression;
import ca.scotthyndman.as2haxe.dom.ASInstanceOfExpression;
import ca.scotthyndman.as2haxe.dom.ASMethodDeclaration;
import ca.scotthyndman.as2haxe.dom.ASMethodInvocation;
import ca.scotthyndman.as2haxe.dom.ASNewExpression;
import ca.scotthyndman.as2haxe.dom.ASNullLiteral;
import ca.scotthyndman.as2haxe.dom.ASNumberLiteral;
import ca.scotthyndman.as2haxe.dom.ASObjectLiteral;
import ca.scotthyndman.as2haxe.dom.ASObjectLiteralField;
import ca.scotthyndman.as2haxe.dom.ASPackage;
import ca.scotthyndman.as2haxe.dom.ASPackageDeclaration;
import ca.scotthyndman.as2haxe.dom.ASParenthesizedExpression;
import ca.scotthyndman.as2haxe.dom.ASPostfixExpression;
import ca.scotthyndman.as2haxe.dom.ASPrefixExpression;
import ca.scotthyndman.as2haxe.dom.ASPropertyDeclaration;
import ca.scotthyndman.as2haxe.dom.ASQualifiedName;
import ca.scotthyndman.as2haxe.dom.ASRegion;
import ca.scotthyndman.as2haxe.dom.ASReturnStatement;
import ca.scotthyndman.as2haxe.dom.ASSimpleName;
import ca.scotthyndman.as2haxe.dom.ASStatement;
import ca.scotthyndman.as2haxe.dom.ASStringLiteral;
import ca.scotthyndman.as2haxe.dom.ASSwitchCase;
import ca.scotthyndman.as2haxe.dom.ASSwitchStatement;
import ca.scotthyndman.as2haxe.dom.AST;
import ca.scotthyndman.as2haxe.dom.ASTNode;
import ca.scotthyndman.as2haxe.dom.ASThrowStatement;
import ca.scotthyndman.as2haxe.dom.ASTryStatement;
import ca.scotthyndman.as2haxe.dom.ASType;
import ca.scotthyndman.as2haxe.dom.ASTypeDeclaration;
import ca.scotthyndman.as2haxe.dom.ASUndefinedLiteral;
import ca.scotthyndman.as2haxe.dom.ASVariableDeclaration;
import ca.scotthyndman.as2haxe.dom.ASVariableDeclarationExpression;
import ca.scotthyndman.as2haxe.dom.ASVariableDeclarationStatement;
import ca.scotthyndman.as2haxe.dom.ASWhileStatement;
import ca.scotthyndman.as2haxe.dom.ASWithStatement;

/**
 * Parses ActionScript 2.0 code files.
 * 
 * @author Martin Schnabel (Original Author)
 * @author Scott Hyndman (AS2Haxe Changes)
 */
public class ASANTLRParser implements AS2TokenTypes {
	private static Logger logger = Logger.getLogger(ASANTLRParser.class);

	private ASCompilationUnit compilationUnit;

	private ASTypeDeclaration returnType;

	private AST ast;

	private IDocument document;

	private List<ASImportDeclaration> importList;

	public ASANTLRParser() {
		this.ast = new AST();
		ASPackage.initWithAST(ast);
	}

	public ASCompilationUnit getDom(IDocument document) throws Exception {
		init(document);
		try {
			parseDocument();
		} catch (RuntimeException e) {
			throw new Exception("Encountered error while creating "
					+ returnType.getName().toString(), e);
		}
		return compilationUnit;
	}

	/**
	 * Returns the abstract syntax tree.
	 * 
	 * @return the ast
	 */
	public AST getAST() {
		return this.ast;
	}

	/**
	 * sets the wordlist and set currentPosition to 0
	 * 
	 * @param document
	 */
	private void init(IDocument document) {
		compilationUnit = new ASCompilationUnit(getAST());
		this.document = document;
		importList = new ArrayList<ASImportDeclaration>();
	}

	/**
	 * Parses the class document.
	 */
	private void parseDocument() {
		AS2Lexer lexer = new AS2Lexer(new ByteArrayInputStream(document
				.get().getBytes()));

		//
		// use tabwidth 1 for right document offset this will result in uneven
		// line/colum info of
		// ast and editor
		// 
		lexer.setTabSize(1);

		//
		// Start the parsing
		//
		AS2Parser parser = new AS2Parser(lexer);
		ASSourceASTFactory astFactory = new ASSourceASTFactory();
		parser.setASTFactory(astFactory);

		try {
			parser.compilationUnit();
			ASSourceAST compilationUnit = (ASSourceAST) parser.getAST();

			if (compilationUnit != null) {
				parseCUnitAST(compilationUnit);
			} else {
				logger.warn("parseDocument got null compilation unit");
			}

			if (returnType != null && lexer.getComments() != null) {
				Iterator commentsIter = lexer.getComments().iterator();
				while (commentsIter.hasNext()) {
					Token commentToken = (Token) commentsIter.next();
					ASSourceAST commentAST = (ASSourceAST) astFactory
							.create(commentToken);

					ASComment comment = new ASComment(this.ast);
					ASRegion region = getRegion(commentAST);
					region = new ASRegion(region.getOffset(), region
							.getLength(), region.getLine(), commentToken
							.getType() == SL_COMMENT ? 0 : region
							.getNumberOfLines() - 1);

					comment.setFullRegion(region);
					comment.setText(commentToken.getText());
					comment.setDocComment(commentToken.getText().startsWith(
							"/**"));

					returnType.comments().add(comment);
				}
			}
		} catch (RecognitionException e) {
			try {
				System.err.println(document.get(document
						.getLineOffset(e.line - 1), document
						.getLineLength(e.line - 1)));
				logger.error("parseDocument recognition error : "
						+ document.get(document.getLineOffset(e.line - 1),
								document.getLineLength(e.line - 1)), e);
			} catch (BadLocationException e1) {
			}
		} catch (TokenStreamException e) {
			logger.error("parseDocument token stream error", e);
		}
	}

	private void parseCUnitAST(ASSourceAST ast) {
		ASSourceAST child = ast.getChild();
		while (child != null) {
			if (child.isRegion()) {
				switch (child.getType()) {
				case IMPORTS:
					createImports(child);
					break;
				case CLASS_DEF:
				case INTERFACE_DEF:
					createType(child);
					break;
				}
			}
			child = child.getSibling();
		}

		//
		// Fill out the compilation unit
		//
		compilationUnit.setTypeDeclaration(returnType);
		compilationUnit.getImports().addAll(importList);

		ASPackageDeclaration pkg = new ASPackageDeclaration(getAST());
		pkg.setName(returnType.getName().getQualifier());
		compilationUnit.setPackageDeclaration(pkg);
	}

	/**
	 * Creates a type DOM object.
	 * 
	 * @param ast
	 *            the AST node
	 */
	private ASTypeDeclaration createType(ASSourceAST ast) {
		ASSourceAST child = ast.getChild();

		returnType = new ASTypeDeclaration(this.ast);
		returnType.setInterface(ast.getType() == INTERFACE_DEF);
		returnType.setFullRegion(getRegion(ast));

		while (child != null) {
			if (child.isRegion()) {
				switch (child.getType()) {
				case MODIFIERS:
					break;
				case IDENT:
					returnType.setName(getQualifiedName(child));
					break;
				case EXTENDS_CLAUSE:
					ASSourceAST extendIdent = child.getChild();
					if (extendIdent == null) {
						logger
								.error("extendIdent is null in createType. start: "
										+ child.getColumn()
										+ " : "
										+ child.getEndColumn());
					} else {
						ASType supType = new ASType(this.ast,
								getQualifiedName(extendIdent));
						returnType.setSuperClassType(supType);
					}
					break;
				case IMPLEMENTS_CLAUSE:
					List<ASType> qNameArray = new ArrayList<ASType>();
					ASSourceAST implementIdent = child.getChild();
					while (implementIdent != null) {
						ASType impType = new ASType(this.ast,
								getQualifiedName(implementIdent));
						qNameArray.add(impType);
						implementIdent = implementIdent.getSibling();
					}
					returnType.getInterfaceTypes().addAll(qNameArray);
					break;
				case BLOCK:
					createTypeBlock(child);
					break;
				}
			}
			child = child.getSibling();
		}

		return returnType;
	}

	/**
	 * Creates the contents of a type.
	 * 
	 * @param ast
	 *            the AST node
	 */
	private void createTypeBlock(ASSourceAST ast) {
		ASSourceAST child = ast.getChild();
		while (child != null) {
			if (child.isRegion()) {
				switch (child.getType()) {
				case VAR_DEF:
				case VAR_SUB_DEF:
					ASVariableDeclarationStatement decl = new ASVariableDeclarationStatement(
							this.ast, createVariableDeclaration(child));

					returnType.bodyDeclarations().add(
							new ASFieldDeclaration(this.ast, decl));
					break;
				case METHOD_DEF:
					ASFunction meth = createMethod(child);
					if (meth.isConstructor()) {
						returnType.bodyDeclarations().add(
								new ASConstructorDeclaration(this.ast, meth));
					} else {
						returnType.bodyDeclarations().add(
								new ASMethodDeclaration(this.ast, meth));
					}
					break;
				}
			}
			child = child.getSibling();
		}
	}

	/**
	 * Creates a method from a corresponding AST node.
	 * 
	 * @param ast
	 *            The AST node
	 * @return The DOM object
	 */
	private ASFunction createMethod(ASSourceAST ast) {
		ASSourceAST child = ast.getChild();
		boolean isPropertySetter = false;
		boolean isPropertyGetter = false;
		boolean hasName = false;

		ASFunction func = new ASFunction(this.ast);
		func.setFullRegion(getRegion(ast));
		func.setStatic(false);

		while (child != null) {
			if (child.isRegion()) {
				switch (child.getType()) {
				case MODIFIERS:
					ASSourceAST modIdent = child.getChild();
					while (modIdent != null) {
						switch (modIdent.getType()) {
						case LITERAL_public:
							func.setPublic(true);
							break;

						case LITERAL_private:
							func.setPrivate(true);
							break;
						case LITERAL_static:
							func.setStatic(true);
							break;
						}
						modIdent = modIdent.getSibling();
					}
					break;
				case IDENT:
					if (func.getName() != null) {
						if (func.getName().toString().equals("set")) {
							isPropertySetter = true;
						} else if (func.getName().toString().equals("get")) {
							isPropertyGetter = true;
						}
					}
					func.setName(getSimpleName(child));
					hasName = true;
					break;
				case PARAMS:
					ASSourceAST paramAST = child.getChild();
					while (paramAST != null) {
						func.getParameters().add(
								createVariableDeclaration(paramAST));
						paramAST = paramAST.getSibling();
					}

					break;
				case TYPE_SPEC:
					ASSourceAST returnIdent = child.getChild();
					if (returnIdent == null) {
						logger
								.error("createMethod: returnIdent is null for TYPE_SPEC");
					} else {
						func.setReturnType(new ASType(this.ast,
								getQualifiedName(returnIdent)));
					}
					break;
				case BLOCK:
					func.setBody(createBlock(child));
					break;
				}
			}
			child = child.getSibling();
		}

		//
		// Deal with getter and setter shit
		//
		if (hasName && (isPropertyGetter || isPropertySetter)) {
			ASPropertyDeclaration property = getOrCreateASProperty(func
					.getName().toString());
			property.setName(new ASSimpleName(this.ast, func
					.getName().toString()));

			if (isPropertyGetter) {
				func.setIsGetter(true);
				property.setGetter(func);

				if (property.getType() != null && func.getReturnType() != null) {
					property.setType(func.getReturnType());
				}
			} else {
				func.setIsSetter(true);
				property.setSetter(func);

				ASVariableDeclaration valueParam = (ASVariableDeclaration) func
						.getParameters().get(0);
				if (property.getType() != null && valueParam != null
						&& valueParam.getType() != null) {
					property.setType(valueParam.getType());
				}

			}
		} else if (hasName) {
			if (func.getName().equals(returnType.getName().getName())) {
				func.setConstructor(true);
			}
		}

		return func;
	}

	/**
	 * Generates a block.
	 * 
	 * @param ast
	 * @return
	 */
	private ASBlock createBlock(ASSourceAST ast) {
		ASBlock block = new ASBlock(this.ast);
		block.setFullRegion(getRegion(ast));

		ASSourceAST child = ast.getChild();
		while (child != null) {
			if (child.isRegion()) {
				ASStatement stmt = createStatement(child);

				if (stmt != null) {
					block.getStatements().add(stmt);
				}
			}

			child = child.getSibling();
		}

		return block;
	}

	/**
	 * Generates a statement.
	 * 
	 * @param method
	 * @param ast
	 */
	private ASStatement createStatement(ASSourceAST ast) {

		ASStatement statement = null;

		switch (ast.getType()) {
		case BLOCK:
			statement = createBlock(ast);
			break;

		case VAR_DEF:
			statement = new ASVariableDeclarationStatement(this.ast,
					createVariableDeclaration(ast));
			break;

		case EXPR_STMNT:
			statement = new ASExpressionStatement(this.ast);
			((ASExpressionStatement) statement)
					.setExpression(createExpression(ast.getChild()));
			break;

		case LITERAL_if:
			ASIfStatement ifs = new ASIfStatement(this.ast);
			statement = ifs;
			ifs.setExpression(createExpression(ast.getChild()));

			//
			// Get then
			//
			ASSourceAST then = ast.getChild().getSibling();
			ifs.setThenStatement(createStatement(then));

			//
			// See if there is an else
			//
			ASSourceAST els = then.getSibling();
			if (els != null && els.getType() == LITERAL_else) {
				ifs.setElseStatement(createStatement(els.getChild()));
			}

			break;

		case LITERAL_for:

			//
			// Get the expressions out
			//
			ASSourceAST forExprs = ast.getChild();
			if (forExprs.getType() == FOR_INIT) { // traditional for
				ASForStatement fors = new ASForStatement(this.ast);
				statement = fors;

				ASSourceAST init = ast.getChild();
				ASSourceAST cond = init.getSibling();
				ASSourceAST iter = cond.getSibling();
				ASSourceAST body = iter.getSibling();

				fors.setInitializers(createExpression(init.getChild()));
				fors.setExpression(createExpression(cond.getChild()));
				fors.setUpdaters(createExpression(iter.getChild()));
				fors.setBody(createStatement(body));
			} else { // for in
				ASForInStatement fors = new ASForInStatement(this.ast);
				statement = fors;

				ASSourceAST key = ast.getChild();
				ASSourceAST expr = key.getSibling();
				ASSourceAST body = expr.getSibling();

				fors.setKeyVariable(createExpression(key));
				fors.setExpression(createExpression(expr));
				fors.setBody(createStatement(body));
			}

			break;

		case LITERAL_while:
			ASWhileStatement whiles = new ASWhileStatement(this.ast);
			statement = whiles;

			ASSourceAST wExpr = ast.getChild();
			whiles.setExpression(createExpression(wExpr));

			ASSourceAST whileBody = wExpr.getSibling();
			whiles.setBody(createStatement(whileBody));

			break;

		case LITERAL_do:
			ASDoStatement dos = new ASDoStatement(this.ast);
			statement = dos;

			ASSourceAST doBody = ast.getChild();
			dos.setBody(createStatement(doBody));

			ASSourceAST expression = doBody.getSibling();
			dos.setExpression(createExpression(expression));
			break;

		case LITERAL_with:
			ASWithStatement withs = new ASWithStatement(this.ast);
			statement = withs;

			ASSourceAST withExpr = ast.getSibling();
			withs.setExpression(createExpression(withExpr));

			ASSourceAST withBody = withExpr.getChild();
			withs.setBody(createStatement(withBody));
			break;

		case LITERAL_switch:
			ASSwitchStatement swtch = new ASSwitchStatement(this.ast);
			statement = swtch;

			//
			// Expression
			//
			ASSourceAST swtchExpr = ast.getChild();
			swtch.setExpression(createExpression(swtchExpr));

			//
			// Switch cases
			//
			ASSourceAST aCase = swtchExpr.getSibling().getChild();
			while (aCase != null) {
				ASSwitchCase switchCase = new ASSwitchCase(this.ast);
				switchCase.setFullRegion(getRegion(ast));

				//
				// Fill case
				//
				ASSourceAST caseExpr = aCase.getChild();
				switchCase.setExpression(createExpression(caseExpr));

				ASSourceAST caseStmt = caseExpr.getSibling();
				while (caseStmt != null) {
					ASStatement ccaseStmt = createStatement(caseStmt);
					switchCase.getStatements().add(ccaseStmt);
					caseStmt = caseStmt.getSibling();
				}

				swtch.getCases().add(switchCase);
				aCase = aCase.getSibling();
			}

			break;

		case LITERAL_break:
			statement = new ASBreakStatement(this.ast);
			break;

		case LITERAL_continue:
			statement = new ASContinueStatement(this.ast);
			break;

		case LITERAL_return:
			ASReturnStatement ret = new ASReturnStatement(this.ast);
			statement = ret;
			if (ast.getFirstChild() != null) {
				ret.setExpression(createExpression(ast.getChild()));
			}
			break;

		case THROW:
			ASThrowStatement thrw = new ASThrowStatement(this.ast);
			statement = thrw;
			thrw.setExpression(createExpression(ast.getChild()));
			break;

		case LITERAL_try:
			ASTryStatement trys = new ASTryStatement(this.ast);
			statement = trys;

			ASSourceAST body = ast.getChild();
			trys.setBody(createBlock(body));

			//
			// Get the catches
			//
			ASSourceAST ctch = body.getSibling();
			while (ctch != null && ctch.getType() == LITERAL_catch) {
				ASCatchStatement ctchs = new ASCatchStatement(this.ast);
				ctchs.setFullRegion(getRegion(ctch));

				//
				// Extract the param and the body
				//
				ASSourceAST param = ctch.getChild();
				ASSourceAST cbody = param.getSibling();
				ctchs.setException(createVariableDeclaration(param));
				ctchs.setBody(createStatement(cbody));

				//
				// Add the catch and move on to the next one
				//
				trys.getCatches().add(ctchs);
				ctch = ctch.getSibling();
			}

			break;
		}

		if (statement != null) {
			statement.setFullRegion(getRegion(ast));
		}

		return statement;

	}

	/**
	 * Creates an expression given an AST node.
	 * 
	 * @param ast
	 * @return
	 */
	private ASExpression createExpression(ASSourceAST ast) {
		ASExpression expr = null;

		if (ast == null) {
			return null;
		}

		//
		// EXPR doesn't mean anything. Parse the child.
		//
		if (EXPR == ast.getType() || ELIST == ast.getType()) {
			return createExpression(ast.getChild());
		}

		switch (ast.getType()) {
		case THIS:
		case IDENT:
			ASFieldAccess facce = new ASFieldAccess(this.ast);
			expr = facce;

			facce.setName(getSimpleName(ast));
			if (ast.getChild() != null) {
				facce.setExpression(createExpression(ast.getChild()));
			}
			break;

		case TRUE:
		case FALSE:
			ASBooleanLiteral bool = new ASBooleanLiteral(this.ast);
			expr = bool;
			bool.setBooleanValue(ast.getType() == TRUE);
			break;

		case NULL:
			expr = new ASNullLiteral(this.ast);
			break;

		case UNDEFINED:
			expr = new ASUndefinedLiteral(this.ast);
			break;

		case STRING_LITERAL:
			ASStringLiteral str = new ASStringLiteral(this.ast);
			expr = str;

			//
			// Get string value
			//
			try {
				str
						.setStringValue(document.get(getOffset(ast),
								getLength(ast)));
			} catch (BadLocationException e) {
				logger.error(e);
			}

			break;

		case NUMBER:
			ASNumberLiteral num = new ASNumberLiteral(this.ast);
			expr = num;

			//
			// Get number value
			//
			String numText = getASTText(ast);
			boolean negative = false;
			if (numText.charAt(0) == '-') {
				negative = true;
				numText = numText.substring(1);
			}
			if (numText.contains("e") || numText.contains(".")) {
				double val = Double.parseDouble(numText);
				if (negative)
					val = -val;
				num.setNumberValue(val);
			} else if (numText.length() > 3
					&& numText.substring(0, 2).toLowerCase().equals("0x")) { // hex
				long val = Long.parseLong(numText.substring(2), 16);
				if (negative)
					val = -val;
				num.setNumberValue(val);
			} else if (numText.charAt(0) == '0') { // octal
				int val = Integer.parseInt(numText, 8);
				if (negative)
					val = -val;
				num.setNumberValue(val);
			} else {
				int val = Integer.parseInt(numText, 10);
				if (negative)
					val = -val;
				num.setNumberValue(val);
			}

			break;

		//
		// Variable definition
		//
		case VAR_DEF:
		case VAR_SUB_DEF:
			expr = new ASVariableDeclarationExpression(this.ast,
					createVariableDeclaration(ast));
			break;

		//
		// Parenthesized
		//
		case ENCPS_EXPR:
			expr = new ASParenthesizedExpression(this.ast, createExpression(ast
					.getChild()));
			break;

		//
		// Instanceof
		//
		case INSTANCEOF:
			ASInstanceOfExpression inofe = new ASInstanceOfExpression(this.ast);
			expr = inofe;

			ASSourceAST ino_l = ast.getChild();
			ASSourceAST ino_r = ino_l.getSibling();
			inofe.setLeftOperand(createExpression(ino_l));
			inofe.setRightOperand(getQualifiedName(ino_r));
			break;

		//
		// Conditional
		// 
		case QUESTION:
			ASConditionalExpression conde = new ASConditionalExpression(
					this.ast);
			expr = conde;

			ASSourceAST subexpr = ast.getChild().getSibling();
			conde.setExpression(createExpression(ast.getChild()));
			conde.setThenExpression(createExpression(subexpr.getChild()));
			conde.setElseExpression(createExpression(subexpr
					.getChild().getSibling()));
			break;

		// 
		// Assign
		// 
		case ASSIGN:
		case STAR_ASSIGN:
		case DIV_ASSIGN:
		case MOD_ASSIGN:
		case PLUS_ASSIGN:
		case MINUS_ASSIGN:
		case SL_ASSIGN:
		case SR_ASSIGN:
		case BSR_ASSIGN:
		case BAND_ASSIGN:
		case BXOR_ASSIGN:
		case BOR_ASSIGN:
			ASAssignment asse = new ASAssignment(this.ast);
			expr = asse;

			//
			// Fill in values
			//			
			ASSourceAST ass_l = ast.getChild();
			ASSourceAST ass_r = ass_l.getSibling();
			asse.setLeftOperand(createExpression(ass_l));
			asse.setRightOperand(createExpression(ass_r));
			asse.setOperator(ASAssignment.Operator.getOperator(ast.getType()));
			break;

		//
		// Postfix
		//
		case POST_INC:
		case POST_DEC:
			ASPostfixExpression poste = new ASPostfixExpression(this.ast);
			expr = poste;
			poste.setOperator(ASPostfixExpression.Operator.getOperator(ast
					.getType()));
			poste.setOperand(createExpression(ast.getChild()));
			break;

		//
		// Prefix
		//
		case INC:
		case DEC:
		case UNARY_MINUS:
		case UNARY_PLUS:
		case LNOT:
		case BNOT:
		case DELETE:
		case TYPEOF:
			ASPrefixExpression pree = new ASPrefixExpression(this.ast);
			expr = pree;
			pree.setOperator(ASPrefixExpression.Operator.getOperator(ast
					.getType()));
			pree.setOperand(createExpression(ast.getChild()));
			break;

		//
		// Infix
		//
		case EQUAL:
		case STRICT_EQUAL:
		case NOT_EQUAL:
		case STRICT_NOT_EQUAL:
		case DIV:
		case PLUS:
		case MINUS:
		case STAR:
		case MOD:
		case SR:
		case BSR:
		case GE:
		case GT:
		case SL:
		case LE:
		case LT:
		case BXOR:
		case BOR:
		case LOR:
		case BAND:
		case LAND:
			ASInfixExpression infixe = new ASInfixExpression(this.ast);
			expr = infixe;

			ASSourceAST inf_l = ast.getChild();
			ASSourceAST inf_r = inf_l.getSibling();
			infixe.setLeftOperand(createExpression(inf_l));
			infixe.setRightOperand(createExpression(inf_r));
			infixe.setOperator(ASInfixExpression.Operator.getOperator(ast
					.getType()));
			break;

		//
		// Instance creation
		//
		case NEW_EXPR:
			ASNewExpression newe = new ASNewExpression(this.ast);
			expr = newe;

			newe.setConstructorCall(createExpression(ast.getChild()));
			break;

		//
		// Array access
		//
		case ARRAY_ACC:
			ASArrayAccess arre = new ASArrayAccess(this.ast);
			expr = arre;

			arre.setArray(createExpression(ast.getChild()));
			arre.setIndex(createExpression(ast.getChild().getSibling()));

			break;

		//
		// Method call
		//
		case METHOD_CALL:
			ASMethodInvocation methe = new ASMethodInvocation(this.ast);
			expr = methe;

			methe.setExpression(createExpression(ast.getChild()));
			if (ast.getChild().getSibling() != null) {
				ASSourceAST metharg = ast.getChild().getSibling().getChild();
				while (metharg != null) {
					ASExpression arg = createExpression(metharg);
					methe.getArguments().add(arg);
					metharg = metharg.getSibling();
				}
			}
			break;

		case FUNC_DEF:
			ASFunctionExpression funce = new ASFunctionExpression(this.ast);
			expr = funce;

			funce.setFunction(createMethod(ast));
			break;

		case ARRAY_LITERAL:
			ASArrayLiteral arrlite = new ASArrayLiteral(this.ast);
			expr = arrlite;

			ASSourceAST ele = ast.getChild();
			while (ele != null) {
				ASExpression arrele = createExpression(ele.getChild());
				arrlite.getElements().add(arrele);
				ele = ele.getSibling();
			}
			break;

		case OBJECT_LITERAL:
			ASObjectLiteral objlite = new ASObjectLiteral(this.ast);
			expr = objlite;

			ASSourceAST field = ast.getChild();
			while (field != null) {
				ASObjectLiteralField f = createObjectField(field);
				if (f != null) {
					objlite.getFields().add(f);
				}
				field = field.getSibling();
			}
			break;

		default:
			expr = new ASExpression(this.ast);
			break;
		}

		expr.setFullRegion(getRegion(ast));
		return expr;
	}

	private ASVariableDeclaration createVariableDeclaration(ASSourceAST ast) {
		ASVariableDeclaration var = new ASVariableDeclaration(this.ast);
		var.setType(new ASType(this.ast, ASQualifiedName.getDefault()));
		var.setFullRegion(getRegion(ast));

		ASSourceAST child = ast.getChild();
		while (child != null) {
			switch (child.getType()) {
			case MODIFIERS:
				ASSourceAST modIdent = child.getChild();
				while (modIdent != null) {
					switch (modIdent.getType()) {
					case LITERAL_private:
						var.setPrivate(true);
						break;
					case LITERAL_static:
						var.setStatic(true);
						break;
					}
					modIdent = modIdent.getSibling();
				}
				break;
			case IDENT:
				var.setName(getSimpleName(child));
				break;
			case TYPE_SPEC:
				ASSourceAST parameterTypeIdent = child.getChild();
				var.setType(new ASType(this.ast,
						getQualifiedName(parameterTypeIdent)));
				break;
			case VAR_INIT:
				ASSourceAST varInit = child.getChild();
				var.setInitializer(createExpression(varInit));
				break;
			}
			child = child.getSibling();
		}

		return var;
	}

	/**
	 * Creates an object field.
	 * 
	 * @param ast
	 * @return
	 */
	private ASObjectLiteralField createObjectField(ASSourceAST ast) {
		if (ast.getType() != OBJECT_FIELD) {
			logger.error("ast of type " + ast.getType() + " passed to "
					+ "createObjectField. Type expected: " + OBJECT_FIELD);
			return null;
		}

		ASObjectLiteralField field = new ASObjectLiteralField(this.ast);
		field.setName(new ASSimpleName(this.ast, ast.getChild().getText()));
		field
				.setValue(createExpression(ast
						.getChild().getSibling().getChild()));

		return field;
	}

	private void createImports(ASSourceAST ast) {
		ASSourceAST child = ast.getChild();
		while (child != null) {
			if (child.isRegion() && child.getType() == IMPORT) {
				createImport(child);
			}
			child = child.getSibling();
		}
	}

	private void createImport(ASSourceAST ast) {
		ASSourceAST child = ast.getChild();

		if (child != null && child.isRegion()
				&& (child.getType() == IDENT || child.getType() == STAR)) {
			ASImportDeclaration asImport = new ASImportDeclaration(this.ast);
			asImport.setName(getQualifiedName(child));
			asImport.setFullRegion(getRegion(ast));
			importList.add(asImport);
		}
	}

	/**
	 * Gets a qualified name given an AST.
	 * 
	 * @param ast
	 *            The AST
	 * @return A qualified name
	 */
	private ASQualifiedName getQualifiedName(ASSourceAST ast) {
		ASQualifiedName qualifiedName = new ASQualifiedName(this.ast);
		qualifiedName.setFullRegion(getRegion(ast));
		ASSimpleName simpleName = getSimpleName(ast);
		qualifiedName.setName(simpleName);

		//
		// Attempt recursion
		//
		ASSourceAST qualiAST = ast.getChild();
		if (qualiAST == null) {
			return qualifiedName;
		}

		qualifiedName.setQualifier(getQualifiedName(qualiAST));

		return qualifiedName;
	}

	/**
	 * Gets a simple name expression given an AST.
	 * 
	 * @param ast
	 *            The AST
	 * @return A simple name
	 */
	private ASSimpleName getSimpleName(ASSourceAST ast) {
		ASSimpleName simpleName = new ASSimpleName(this.ast, ast.getText());
		simpleName.setFullRegion(getRegion(ast));
		return simpleName;
	}

	private String getASTText(ASSourceAST ast) {
		try {
			return document.get(getOffset(ast), getLength(ast));
		} catch (BadLocationException e) {
			logger.error(e);
			return null;
		}
	}

	/**
	 * Returns a region for an AST node.
	 * 
	 * @param ast
	 * @return
	 */
	private ASRegion getRegion(ASSourceAST ast) {
		int offset = getOffset(ast);
		int length = getLength(ast);
		int line;
		int numLines;
		try {
			line = document.getLineOfOffset(offset);
			numLines = document.getLineOfOffset(offset + length) - line + 1;
		} catch (BadLocationException e) {
			logger.error(e);
			return null;
		}

		return new ASRegion(offset, length, line, numLines);
	}

	private int getOffset(ASSourceAST ast) {
		int offset = -1;
		try {
			offset = document.getLineOffset(ast.getLine() - 1)
					+ ast.getColumn() - 1;
		} catch (BadLocationException e) {
			logger.warn("getOffset caused bad location", e);
		}
		return offset;
	}

	private int getLength(ASSourceAST ast) {
		return getEndOffset(ast) - getOffset(ast);
	}

	private int getEndOffset(ASSourceAST ast) {
		int offset = -1;
		try {
			offset = document.getLineOffset(ast.getEndLine() - 1)
					+ ast.getEndColumn() - 1;
		} catch (BadLocationException e) {
			logger.error("getEndOffset caused BadLocation : "
					+ ast.getEndLine() + " : " + ast.getEndColumn(), e);
		}
		return offset;
	}

	/**
	 * Creates a property only if it does not yet exist in the return type.
	 * 
	 * @param name
	 *            the name of the property
	 * @return the property
	 */
	private ASPropertyDeclaration getOrCreateASProperty(String name) {
		for (ASTNode n : returnType.bodyDeclarations()) {
			if (!(n instanceof ASPropertyDeclaration)) {
				continue;
			}

			ASPropertyDeclaration prop = (ASPropertyDeclaration) n;
			if (prop.getName().toString().equals(name)) {
				return prop;
			}
		}

		ASPropertyDeclaration property = new ASPropertyDeclaration(this.ast);
		returnType.bodyDeclarations().add(property);

		return property;
	}
}
